package de.machmireinebook.epubeditor.epublib.domain.epub2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import de.machmireinebook.epubeditor.epublib.domain.EpubIdentifier;
import de.machmireinebook.epubeditor.epublib.domain.EpubMetadata;

/**
 * A Book's collection of Metadata.
 * All elements as described and defined in http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.2.9
 *
 * @author paul
 */
public class Metadata implements Serializable, EpubMetadata
{
    private static final long serialVersionUID = -2437262888962149444L;

    public static final DublinCoreMetadataElement DEFAULT_LANGUAGE = new DublinCoreMetadataElement("en");

    private boolean autoGeneratedId;
    private List<Author> authors = new ArrayList<>();
    private List<Author> contributors = new ArrayList<>();
    private List<MetadataDate> dates = new ArrayList<>();
    private List<DublinCoreMetadataElement> languages = Arrays.asList(DEFAULT_LANGUAGE);
    private List<DublinCoreMetadataElement> formats = new ArrayList<>();
    private List<DublinCoreMetadataElement> rights = new ArrayList<>();
    private List<DublinCoreMetadataElement> sources = new ArrayList<>();
    private List<DublinCoreMetadataElement> titles = new ArrayList<>();
    private List<Identifier> identifiers = new ArrayList<>();
    private List<DublinCoreMetadataElement> subjects = new ArrayList<>();
    private List<DublinCoreMetadataElement> types = new ArrayList<>();
    private List<DublinCoreMetadataElement> descriptions = new ArrayList<>();
    private List<DublinCoreMetadataElement> publishers = new ArrayList<>();
    private List<DublinCoreMetadataElement> relations = new ArrayList<>();
    private List<DublinCoreMetadataElement> coverages = new ArrayList<>();
    private Map<String, String> epub2MetaAttributes = new HashMap<>();

    public Metadata()
    {
        identifiers.add(new Identifier());
        autoGeneratedId = true;
    }

    public boolean isAutoGeneratedId()
    {
        return autoGeneratedId;
    }

    public MetadataDate addDate(MetadataDate date)
    {
        this.dates.add(date);
        return date;
    }

    public List<MetadataDate> getDates()
    {
        return dates;
    }

    public void setDates(List<MetadataDate> dates)
    {
        this.dates = dates;
    }

    public Author addAuthor(Author author)
    {
        authors.add(author);
        return author;
    }

    public List<Author> getAuthors()
    {
        return authors;
    }

    public List<Author> getAuthorsWithoutFirst()
    {
        if (authors.size() > 1)
        {
            return authors.subList(1, authors.size());
        }
        else
        {
            return new ArrayList<>();
        }
    }

    public void setAuthors(List<Author> authors)
    {
        this.authors = authors;
    }

    public Author getFirstAuthor()
    {
        if (authors == null || authors.isEmpty())
        {
            return null;
        }
        for (Author author : authors)
        {
            if (author != null)
            {
                return author;
            }
        }
        return null;
    }

    public Author addContributor(Author contributor)
    {
        contributors.add(contributor);
        return contributor;
    }

    public List<Author> getContributors()
    {
        return contributors;
    }

    public void setContributors(List<Author> contributors)
    {
        this.contributors = contributors;
    }

    public List<DublinCoreMetadataElement> getLanguages()
    {
        return languages;
    }

    public void setLanguages(List<DublinCoreMetadataElement> languages)
    {
        this.languages = languages;
    }

    public List<DublinCoreMetadataElement> getSubjects()
    {
        return subjects;
    }

    public void setSubjects(List<DublinCoreMetadataElement> subjects)
    {
        this.subjects = subjects;
    }

    public void setRights(List<DublinCoreMetadataElement> rights)
    {
        this.rights = rights;
    }

    public List<DublinCoreMetadataElement> getRights()
    {
        return rights;
    }

    public List<DublinCoreMetadataElement> getFormats() {
        return formats;
    }

    public void setFormats(List<DublinCoreMetadataElement> formats) {
        this.formats = formats;
    }

    /**
     * Gets the first non-blank title of the book.
     * Will return "" if no title found.
     *
     * @return the first non-blank title of the book.
     */
    public String getFirstTitle()
    {
        if (titles == null || titles.isEmpty())
        {
            return "";
        }
        for (DublinCoreMetadataElement title : titles)
        {
            if (StringUtils.isNotBlank(title.getValue()))
            {
                return title.getValue();
            }
        }
        return "";
    }


    public void addTitle(DublinCoreMetadataElement title)
    {
        titles.add(title);
    }

    public void setTitles(List<DublinCoreMetadataElement> titles)
    {
        this.titles = titles;
    }

    public List<DublinCoreMetadataElement> getTitles()
    {
        return titles;
    }

    public DublinCoreMetadataElement addPublisher(DublinCoreMetadataElement publisher)
    {
        this.publishers.add(publisher);
        return publisher;
    }

    public void setPublishers(List<DublinCoreMetadataElement> publishers)
    {
        this.publishers = publishers;
    }

    public List<DublinCoreMetadataElement> getPublishers()
    {
        return publishers;
    }

    public DublinCoreMetadataElement addDescription(DublinCoreMetadataElement description)
    {
        this.descriptions.add(description);
        return description;
    }

    public void setDescriptions(List<DublinCoreMetadataElement> descriptions)
    {
        this.descriptions = descriptions;
    }

    public List<DublinCoreMetadataElement> getDescriptions()
    {
        return descriptions;
    }

    public Identifier addIdentifier(Identifier identifier)
    {
        if (autoGeneratedId && (!(identifiers.isEmpty())))
        {
            identifiers.set(0, identifier);
        }
        else
        {
            identifiers.add(identifier);
        }
        autoGeneratedId = false;
        return identifier;
    }

    public void setIdentifiers(List<Identifier> identifiers)
    {
        this.identifiers = identifiers;
        autoGeneratedId = false;
    }

    /**
     * The first identifier for which the bookId is true is made the bookId identifier.
     * If no identifier has bookId == true then the first bookId identifier is written as the primary.
     *
     * @param identifiers
     * @return The first identifier for which the bookId is true is made the bookId identifier.
     */
    public Identifier getBookIdIdentifier()
    {
        if(identifiers == null || identifiers.isEmpty())
        {
            return null;
        }

        Identifier result = null;
        for(Identifier identifier: identifiers)
        {
            if(identifier.isBookId())
            {
                result = identifier;
                break;
            }
        }

        if(result == null)
        {
            result = identifiers.get(0);
        }

        return result;
    }

    public void generateNewUuid()
    {
        Identifier uuid = new Identifier();
        Identifier bookId = getBookIdIdentifier();
        bookId.setScheme("UUID");
        bookId.setValue(uuid.getValue());
    }

    public List<EpubIdentifier> getIdentifiers()
    {
        return Collections.unmodifiableList(identifiers);
    }

    public List<Identifier> getEpub2Identifiers()
    {
        return identifiers;
    }

    public DublinCoreMetadataElement addType(DublinCoreMetadataElement type)
    {
        this.types.add(type);
        return type;
    }

    public List<DublinCoreMetadataElement> getTypes()
    {
        return types;
    }

    public void setTypes(List<DublinCoreMetadataElement> types)
    {
        this.types = types;
    }

    public DublinCoreMetadataElement addCoverage(DublinCoreMetadataElement coverage)
    {
        this.coverages.add(coverage);
        return coverage;
    }

    public List<DublinCoreMetadataElement> getCoverages()
    {
        return coverages;
    }

    public void setCoverages(List<DublinCoreMetadataElement> coverages)
    {
        this.coverages = coverages;
    }

    public List<DublinCoreMetadataElement> getRelations() {
        return relations;
    }

    public void setRelations(List<DublinCoreMetadataElement> relations) {
        this.relations = relations;
    }

    public String getEpub2MetaAttribute(String name)
    {
        return epub2MetaAttributes.get(name);
    }

    public void setEpub2MetaAttributes(Map<String, String> metaAttributes)
    {
        this.epub2MetaAttributes = metaAttributes;
    }

    public List<DublinCoreMetadataElement> getSources()
    {
        return sources;
    }

    public void setSources(List<DublinCoreMetadataElement> sources)
    {
        this.sources = sources;
    }

    @Override
    public String getLanguage()
    {
        if (!getLanguages().isEmpty())
        {
            return getLanguages().get(0).getLanguage();
        }
        else
        {
            return null;
        }
    }

}
